/*
 * hapfile.h
 *
 *  Created on: Jul 28, 2010
 *      Author: Pierre Lindenbaum PhD
 *              plindenbaum@yahoo.fr
 *              http://plindenbaum.blogspot.com
 *              
 */
#ifndef HAPFILE_H
#define HAPFILE_H
#include <vector>
#include <fstream>
#include <string>
#include <stdexcept>
#include <cerrno>
#include <memory>
#include "markers.h"

class HapFileFactory;

class Haplotype
    {
    private:
	    char opcode;
    public:
	Haplotype(const Haplotype& cp):opcode(cp.opcode)
	    {
	    }
	 Haplotype(int a0,int a1)
	     {
	     switch(a0)
		{
		case 0: switch (a1) {case 0: return opcode=0;break;  case 1: opcode= 1 ;break; default: break; } break;
		case 1: switch (a1) {case 0: return opcode=10;break; case 1: opcode= 11;break; default: break; } break;
		default: break;
		}
	    throw std::runtime_error("bad opcode");
	    }
	 ~Haplotype()
	     {
	     }

	 int at(int index) const
	     {
	     assert(index==0 || index==1);
	     switch(opcode)
		{
		case 0: return 0;
		case 1: return (index==0?0:1);
		case 10: return (index==0?1:0);
		case 11: return 1;
		}
	     throw std::runtime_error("bad opcode");
	     }

	 int a0() const
	     {
	     return at(0);
	     }
	 int a1() const
	     {
	     return at(1);
	     }

	 char a0(const MarkerPtr marker) const
	     {
	     return a0()==0?marker->a0():marker->a1();
	     }

	 char a1(const MarkerPtr marker) const
	     {
	     return a1()==0?marker->a0():marker->a1();
	     }

	 Haplotype swap() const
	     {
	     Haplotype cp(*this);
	     switch(opcode)
		    {
		    case 0: break;
		    case 1:  cp.opcode=10;break;
		    case 10: cp.opcode=1;break;
		    case 11: break;
		    }
	     return cp;
	     }

    friend std::ostream& operator << (std::ostream& out,const Haplotype& o);
    };

std::ostream& operator << (std::ostream& out,const Haplotype& o)
    {
    out << o.a0()<< " "<< o.a1();
    return out;
    }

/**
 * HapFile
 */
class HapFile
    {
    private:
	    const MarkerListPtr _markers;
	    int _n_individuals;
	    std::vector<Haplotype> haplotypes;

	HapFile(const MarkerListPtr markers):_markers(markers),_n_individuals(0)
	    {

	    }

    public:
	virtual ~HapFile()
	    {

	    }


	const MarkerListPtr markers() const
	    {
	    return _markers;
	    }

	const const MarkerPtr markerAt(int i) const
	    {
	    return markers()->at(i);
	    }

	int individualCount() const
	    {
	    return _n_individuals;
	    }

	int markerCount() const
	    {
	    return markers()->size();
	    }
    public:
	 const Haplotype& at(int markedIndex,int individualIndex) const
	    {
	    assert(markedIndex>=0 && markedIndex< markerCount());
	    assert(individualIndex>=0 && individualIndex< individualCount());
	    return haplotypes.at(markedIndex*individualCount()+individualIndex);
	    }

    friend class HapFileFactory;
    };
typedef HapFile* HapFilePtr;

class HapFileFactory
    {
    private:
    	    void crlf(std::string& line)
    		{
    		if(!line.empty() && line.at(line.size()-1)=='\r')
    	    		{
    	    		line.erase(line.size()-1,1);
    	    		}
    		}
    public:
	HapFileFactory()
	    {
	    }
	~HapFileFactory()
	    {

	    }
	HapFilePtr parse(const char* filename,const MarkerListPtr markers)
	    {
	    int nLines=0;
	    std::string line;
	    u915::io::gzinbuf buff(filename,100000);
	    std::iostream in(&buff);


	    std::auto_ptr<HapFile> hapfile=std::auto_ptr<HapFile>(new HapFile(markers));
	    while(std::getline(in,line))
		{
		crlf(line);
		nLines++;
		std::string::size_type i=0;
		int n_individuals=0;
		while(i< line.size())
		    {
		    if(i+3>line.size())
			{
			std::ostringstream os;
			os << "Error in " << filename << " expected at least 3 chars in " << line.substr(i);
			throw std::runtime_error(os.str());
			}
		    char c0= line.at(i);
		    if(!(c0=='0' || c0=='1'))
			{
		    	std::ostringstream os;
		    	os << "Error in " << filename << " expected 0 or 1 in " << line << " rows but found " <<  c0;
		    	throw std::runtime_error(os.str());
			}
		    char c1= line.at(i+1);
		    if(!(c1=='\t' || c1==' '))
			{
			std::ostringstream os;
			os << "Error in " << filename << " expected a blank in " << line << " rows but found " <<  c1;
			throw std::runtime_error(os.str());
			}
		    char c2= line.at(i+2);
		    if(!(c2=='0' || c2=='1'))
			{
			std::ostringstream os;
			os << "Error in " << filename << " expected 0 or 1 in " << line << " rows but found " <<  c2;
			throw std::runtime_error(os.str());
			}

		    hapfile->haplotypes.push_back(Haplotype(c0-'0',c2-'0'));
		    ++n_individuals;

		    if(i+3== line.size()) break;

		    c1= line.at(i+3);
		    if(!(c1=='\t' || c1==' '))
			{
			std::ostringstream os;
			os << "Error in " << filename << " expected a blank in " << line << " rows but found " <<  c1;
			throw std::runtime_error(os.str());
			}
		    i+=4;
		    }
		if(nLines==1)
		    {
		    if(n_individuals==0)
			{
			std::ostringstream os;
			os << "Error in \"" << filename << "\" not individual found ";

			throw std::runtime_error(os.str());
			}

		    hapfile->_n_individuals=n_individuals;
		    hapfile->haplotypes.reserve(n_individuals*hapfile->_markers->size());
		    }
		else if(hapfile->_n_individuals!=n_individuals)
		    {
		    std::ostringstream os;
		    os << "Error in \"" << filename << "\"  in " << line <<
			" found " << n_individuals <<  " individuals but expected "
			<<  hapfile->_n_individuals
			;
		    throw std::runtime_error(os.str());
		    }
		}


	    if(nLines!=hapfile->_markers->size())
		{
		std::ostringstream os;
		os << "Error in " << filename << " expected " << hapfile->_markers->size() << " rows but found " << nLines;
		throw std::runtime_error(os.str());
		}
	    return hapfile.release();
	    }
    };

#endif
